﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web.UI;
using NUnit.Framework;
using OpenWaves.Reflection;

namespace OpenWaves.Testing.CodeAnalysisRules
{
    public class DeclaredWebResourcesShouldBeEmbededInAssembly : ICodeAnalysisRule
    {
        private static readonly Dictionary<string, string> WebResourceContentTypeMapping =
            new Dictionary<string, string>
                {
                    {"js", ContentTypes.Text.JavaScript},
                    {"png", ContentTypes.Image.Png},
                    {"gif", ContentTypes.Image.Gif},
                    {"jpg", ContentTypes.Image.Jpeg},
                    {"css", ContentTypes.Text.StyleSheet},
                    {"xap", ContentTypes.Application.Silverlight},
                    {"swf", ContentTypes.Application.Flash},
                };

        public void Run(Assembly assembly)
        {
            var declaredWebResources = assembly.GetCustomAttributes<WebResourceAttribute>();

            foreach (var webResource in declaredWebResources)
            {
                VerifyDeclaredWebResource(webResource, assembly);

                if (webResource.ContentType == ContentTypes.Text.StyleSheet)
                {
                    VerifyStyleSheetImageSubstitution(assembly, webResource);
                }
            }
        }

        private static void VerifyDeclaredWebResource(WebResourceAttribute webResourceAttribute, Assembly assembly)
        {
            var webResourceName = webResourceAttribute.WebResource;

            var stream = assembly.GetManifestResourceStream(webResourceName);
            Assert.IsNotNull(stream, "Declared WebResource {0} not found.", webResourceName);

            var extension = webResourceName.Substring(webResourceName.LastIndexOf(".") + 1);
            string expectedContentType;
            if (WebResourceContentTypeMapping.TryGetValue(extension, out expectedContentType) == false)
            {
                Assert.Fail("Content type mapping for '{0}' not defined ('{1}')", extension, webResourceName);
            }

            Assert.AreEqual(expectedContentType, webResourceAttribute.ContentType, "Invalid content type for declared web resource '{0}'", webResourceName);

            if (webResourceAttribute.PerformSubstitution)
            {
                VerifySubstitutions(new StreamReader(stream).ReadToEnd(), assembly.GetCustomAttributes<WebResourceAttribute>().Select(a => a.WebResource).ToList());
            }
        }

        private static void VerifyStyleSheetImageSubstitution(Assembly assembly, WebResourceAttribute webResourceAttribute)
        {

            string styleSheetContent = GetResourceContent(webResourceAttribute, assembly);

            var matches = Regex.Matches(styleSheetContent, @"url\(\s*([^\)]*)\s*\)");

            List<string> invalidImageReferences = matches
                .Cast<Match>()
                .Select(match => match.Groups[1].Value)
                .Where(IsWebResourceReference)
                .ToList();

            if (invalidImageReferences.Count > 0)
            {
                Assert.Fail("Web resource '{0}' seems to have a static reference to an image {1}{2}.", webResourceAttribute.WebResource,
                    Environment.NewLine,
                    invalidImageReferences.JoinStrings(Environment.NewLine));
            }
        }

        private static string GetResourceContent(WebResourceAttribute webResourceAttribute, Assembly assembly)
        {
            var webResourceName = webResourceAttribute.WebResource;

            using (var stream = assembly.GetManifestResourceStream(webResourceName))
            {
                Assert.IsNotNull(stream, "Web Resource {0} not found.", webResourceName);

                using (StreamReader reader = new StreamReader(stream))
                {
                    var content = reader.ReadToEnd();
                    return content;
                }
            }
        }

        private static bool IsWebResourceReference(string imageUrl)
        {
            return imageUrl.Contains("<%=") == false || imageUrl.Contains("WebResource") == false;
        }

        private static void VerifySubstitutions(string content, IEnumerable<string> declaredWebResources)
        {
            var matches = Regex.Matches(content, @"WebResource\s*\(\s*\""(.*)\""\s*\)");

            foreach (Match match in matches)
            {
                var resourceName = match.Groups[1].Value;
                if (declaredWebResources.Contains(resourceName) == false)
                {
                    Assert.Fail("Resource '{0}' is not declared as web resource.", resourceName);
                }
            }
        }

        private static void PrintResources(Assembly assembly)
        {
            Debug.WriteLine(assembly.GetManifestResourceNames()
                .OrderBy(s => s)
                .JoinStrings(Environment.NewLine));
        }
    }
}